// Copyright 2016 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#ifndef CONTENT_BROWSER_FIND_REQUEST_MANAGER_H_
#define CONTENT_BROWSER_FIND_REQUEST_MANAGER_H_

#include <queue>
#include <unordered_map>
#include <unordered_set>
#include <utility>
#include <vector>

#include "content/common/content_export.h"
#include "content/public/browser/web_contents_observer.h"
#include "content/public/common/stop_find_action.h"
#include "third_party/WebKit/public/web/WebFindOptions.h"
#include "ui/gfx/geometry/rect.h"
#include "ui/gfx/geometry/rect_f.h"

namespace content {

class RenderFrameHost;
class WebContentsImpl;

// FindRequestManager manages all of the find-in-page requests/replies
// initiated/received through a WebContents. It coordinates searching across
// multiple (potentially out-of-process) frames, handles the aggregation of find
// results from each frame, and facilitates active match traversal. It is
// instantiated once per WebContents, and is owned by that WebContents.
class CONTENT_EXPORT FindRequestManager : public WebContentsObserver {
public:
    explicit FindRequestManager(WebContentsImpl* web_contents);
    ~FindRequestManager() override;

    // Initiates a find operation for |search_text| with the options specified in
    // |options|. |request_id| uniquely identifies the find request.
    void Find(int request_id,
        const base::string16& search_text,
        const blink::WebFindOptions& options);

    // Stops the active find session and clears the general highlighting of the
    // matches. |action| determines whether the last active match (if any) will be
    // activated, cleared, or remain highlighted.
    void StopFinding(StopFindAction action);

    // Called when a reply is received from a frame with the results from a
    // find request.
    void OnFindReply(RenderFrameHost* rfh,
        int request_id,
        int number_of_matches,
        const gfx::Rect& selection_rect,
        int active_match_ordinal,
        bool final_update);

    // Removes a frame from the set of frames being searched. This should be
    // called whenever a frame is discovered to no longer exist.
    void RemoveFrame(RenderFrameHost* rfh);

#if defined(OS_ANDROID)
    // Selects and zooms to the find result nearest to the point (x, y), defined
    // in find-in-page coordinates.
    void ActivateNearestFindResult(float x, float y);

    // Called when a reply is received from a frame in response to the
    // GetNearestFindResult IPC.
    void OnGetNearestFindResultReply(RenderFrameHost* rfh,
        int nearest_find_result_request_id,
        float distance);

    // Requests the rects of the current find matches from the renderer process.
    void RequestFindMatchRects(int current_version);

    // Called when a reply is received from a frame in response to a request for
    // find match rects.
    void OnFindMatchRectsReply(RenderFrameHost* rfh,
        int version,
        const std::vector<gfx::RectF>& rects,
        const gfx::RectF& active_rect);
#endif

private:
    // An invalid ID. This value is invalid for any render process ID, render
    // frame ID, find request ID, or find match rects version number.
    static const int kInvalidId;

    // The request data for a single find request.
    struct FindRequest {
        // The find request ID that uniquely identifies this find request.
        int id = kInvalidId;

        // The text that is being searched for in this find request.
        base::string16 search_text;

        // The set of find options in effect for this find request.
        blink::WebFindOptions options;

        FindRequest() = default;
        FindRequest(int id,
            const base::string16& search_text,
            const blink::WebFindOptions& options)
            : id(id)
            , search_text(search_text)
            , options(options)
        {
        }
    };

    // WebContentsObserver implementation.
    void DidFinishLoad(RenderFrameHost* rfh, const GURL& validated_url) override;
    void RenderFrameDeleted(RenderFrameHost* rfh) override;
    void RenderFrameHostChanged(RenderFrameHost* old_host,
        RenderFrameHost* new_host) override;
    void FrameDeleted(RenderFrameHost* rfh) override;

    // Resets all of the per-session state for a new find-in-page session.
    void Reset(const FindRequest& initial_request);

    // Called internally as find requests come up in the queue.
    void FindInternal(const FindRequest& request);

    // Called when an informative response (a response with enough information to
    // be able to route subsequent find requests) comes in for the find request
    // with ID |request_id|. Advances the |find_request_queue_| if appropriate.
    void AdvanceQueue(int request_id);

    // Sends a find IPC containing the find request |request| to the RenderFrame
    // associated with |rfh|.
    void SendFindIPC(const FindRequest& request, RenderFrameHost* rfh);

    // Sends the find results (as they currently are) to the WebContents.
    void NotifyFindReply(int request_id, bool final_update);

    // Returns the initial frame in search order. This will be either the first
    // frame, if searching forward, or the last frame, if searching backward.
    RenderFrameHost* GetInitialFrame(bool forward) const;

    // Traverses the frame tree to find and return the next RenderFrameHost after
    // |from_rfh| in search order. |forward| indicates whether the frame tree
    // should be traversed forward (if true) or backward (if false). If
    // |matches_only| is set, then the frame tree will be traversed until the
    // first frame is found for which matches have been found. If |wrap| is set,
    // then the traversal can wrap around past the last frame to the first one (or
    // vice-versa, if |forward| == false). If no frame can be found under these
    // conditions, nullptr is returned.
    RenderFrameHost* Traverse(RenderFrameHost* from_rfh,
        bool forward,
        bool matches_only,
        bool wrap) const;

    // Adds a frame to the set of frames that are being searched. The new frame
    // will automatically be searched when added, using the same options (stored
    // in |current_request_.options|). |force| should be set to true when a
    // dynamic content change is suspected, which will treat the frame as a newly
    // added frame even if it has already been searched. This will force a
    // re-search of the frame.
    void AddFrame(RenderFrameHost* rfh, bool force);

    // Returns whether |rfh| is in the set of frames being searched in the current
    // find session.
    bool CheckFrame(RenderFrameHost* rfh) const;

    // Computes and updates |active_match_ordinal_| based on |active_frame_| and
    // |relative_active_match_ordinal_|.
    void UpdateActiveMatchOrdinal();

    // Called when all pending find replies have been received for the find
    // request with ID |request_id|. The final update was received from |rfh|.
    //
    // Note that this is the final update for this particular find request, but
    // not necessarily for all issued requests. If there are still pending replies
    // expected for a previous find request, then the outgoing find reply issued
    // from this function will not be marked final.
    void FinalUpdateReceived(int request_id, RenderFrameHost* rfh);

#if defined(OS_ANDROID)
    // Called when a nearest find result reply is no longer pending for a frame.
    void RemoveNearestFindResultPendingReply(RenderFrameHost* rfh);

    // Called when a find match rects reply is no longer pending for a frame.
    void RemoveFindMatchRectsPendingReply(RenderFrameHost* rfh);

    // State related to ActivateNearestFindResult requests.
    struct ActivateNearestFindResultState {
        // An ID to uniquely identify the current nearest find result request and
        // its replies.
        int current_request_id = kInvalidId;

        // The x value of the requested point, in find-in-page coordinates.
        float x = 0.0f;

        // The y value of the requested point, in find-in-page coordinates.
        float y = 0.0f;

        // The distance to the nearest result found so far.
        float nearest_distance = FLT_MAX;

        // The frame containing the nearest result found so far.
        RenderFrameHost* nearest_frame = nullptr;

        // Nearest find result replies are still pending for these frames.
        std::unordered_set<RenderFrameHost*> pending_replies;

        ActivateNearestFindResultState();
        ActivateNearestFindResultState(float x, float y);
        ~ActivateNearestFindResultState();

        static int GetNextID()
        {
            static int next_id = 0;
            return next_id++;
        }
    } activate_;

    // Data for find match rects in a single frame.
    struct FrameRects {
        // The rects contained in a single frame.
        std::vector<gfx::RectF> rects;

        // The version number for these rects, as reported by their containing
        // frame. This version is incremented independently in each frame.
        int version = kInvalidId;

        FrameRects();
        FrameRects(const std::vector<gfx::RectF>& rects, int version);
        ~FrameRects();
    };

    // State related to FindMatchRects requests.
    struct FindMatchRectsState {
        // The latest find match rects version known by the requester. This will be
        // compared to |known_version_| after polling frames for updates to their
        // match rects, in order to determine if the requester already has the
        // latest version of rects or not.
        int request_version = kInvalidId;

        // The current overall find match rects version known by
        // FindRequestManager. This version should be incremented whenever
        // |frame_rects| is updated.
        int known_version = 0;

        // A map from each frame to its find match rects.
        std::unordered_map<RenderFrameHost*, FrameRects> frame_rects;

        // The active find match rect.
        gfx::RectF active_rect;

        // Find match rects replies are still pending for these frames.
        std::unordered_set<RenderFrameHost*> pending_replies;

        FindMatchRectsState();
        ~FindMatchRectsState();
    } match_rects_;
#endif

    // The WebContents that owns this FindRequestManager.
    WebContentsImpl* const contents_;

    // The request ID of the initial find request in the current find-in-page
    // session, which uniquely identifies this session. Request IDs are included
    // in all find-related IPCs, which allows reply IPCs containing results from
    // previous sessions (with |request_id| < |current_session_id_|) to be easily
    // identified and ignored.
    int current_session_id_;

    // The current find request.
    FindRequest current_request_;

    // The set of frames that are still expected to reply to a pending initial
    // find request. Frames are removed from |pending_initial_replies_| when their
    // reply to the initial find request is received with |final_update| set to
    // true.
    std::unordered_set<RenderFrameHost*> pending_initial_replies_;

    // The frame (if any) that is still expected to reply to the last pending
    // "find next" request.
    RenderFrameHost* pending_find_next_reply_;

    // Indicates whether an update to the active match ordinal is expected. Once
    // set, |pending_active_match_ordinal_| will not reset until an update to the
    // active match ordinal is received in response to the find request with ID
    // |current_request_.id| (the latest request).
    bool pending_active_match_ordinal_;

    // The number of matches found in each frame. There will necessarily be
    // entries in this map for every frame that is being (or has been) searched in
    // the current find session, and no other frames.
    std::unordered_map<RenderFrameHost*, int> matches_per_frame_;

    // The total number of matches found in the current find-in-page session. This
    // should always be equal to the sum of all the entries in
    // |matches_per_frame_|.
    int number_of_matches_;

    // The frame containing the active match, if one exists, or nullptr otherwise.
    RenderFrameHost* active_frame_;

    // The active match ordinal relative to the matches found in its own frame.
    int relative_active_match_ordinal_;

    // The overall active match ordinal for the current find-in-page session.
    int active_match_ordinal_;

    // The rectangle around the active match, in screen coordinates.
    gfx::Rect selection_rect_;

    // Find requests are queued here when previous requests need to be handled
    // before these ones can be properly routed.
    std::queue<FindRequest> find_request_queue_;

    // Keeps track of the find request ID of the last find reply reported via
    // NotifyFindReply().
    int last_reported_id_;
};

} // namespace content

#endif // CONTENT_BROWSER_FIND_REQUEST_MANAGER_H_
